﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace Aeon.Emulator.Decoding.Emitters
{
    /// <summary>
    /// Emitter for decoding a known register.
    /// </summary>
    internal sealed class LoadKnownRegister : Emitter
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the LoadKnownRegister class.
        /// </summary>
        /// <param name="state">Common emitter state information.</param>
        /// <param name="register">Register for which a decoder is emitted.</param>
        public LoadKnownRegister(EmitStateInfo state, OperandType register)
            : base(state)
        {
            if(!IsKnownRegister(register))
                throw new ArgumentException("Argument is not valid.", "register");

            this.Register = register;
        }
        /// <summary>
        /// Initializes static fields of the LoadKnownRegister class.
        /// </summary>
        static LoadKnownRegister()
        {
            registerFields[OperandType.RegisterAX] = new RegisterInfo(Infos.Processor.PAX, RegisterType.Word);
            registerFields[OperandType.RegisterBX] = new RegisterInfo(Infos.Processor.PBX, RegisterType.Word);
            registerFields[OperandType.RegisterCX] = new RegisterInfo(Infos.Processor.PCX, RegisterType.Word);
            registerFields[OperandType.RegisterDX] = new RegisterInfo(Infos.Processor.PDX, RegisterType.Word);

            registerFields[OperandType.RegisterAL] = new RegisterInfo(Infos.Processor.PAX, RegisterType.Byte);
            registerFields[OperandType.RegisterBL] = new RegisterInfo(Infos.Processor.PBX, RegisterType.Byte);
            registerFields[OperandType.RegisterCL] = new RegisterInfo(Infos.Processor.PCX, RegisterType.Byte);
            registerFields[OperandType.RegisterDL] = new RegisterInfo(Infos.Processor.PDX, RegisterType.Byte);

            registerFields[OperandType.RegisterAH] = new RegisterInfo(Infos.Processor.PAH, RegisterType.Byte);
            registerFields[OperandType.RegisterBH] = new RegisterInfo(Infos.Processor.PBH, RegisterType.Byte);
            registerFields[OperandType.RegisterCH] = new RegisterInfo(Infos.Processor.PCH, RegisterType.Byte);
            registerFields[OperandType.RegisterDH] = new RegisterInfo(Infos.Processor.PDH, RegisterType.Byte);

            registerFields[OperandType.RegisterBP] = new RegisterInfo(Infos.Processor.PBP, RegisterType.Word);
            registerFields[OperandType.RegisterSI] = new RegisterInfo(Infos.Processor.PSI, RegisterType.Word);
            registerFields[OperandType.RegisterDI] = new RegisterInfo(Infos.Processor.PDI, RegisterType.Word);
            registerFields[OperandType.RegisterSP] = new RegisterInfo(Infos.Processor.PSP, RegisterType.Word);

            registerFields[OperandType.RegisterST0] = new RegisterInfo(0);
            registerFields[OperandType.RegisterST1] = new RegisterInfo(1);
            registerFields[OperandType.RegisterST2] = new RegisterInfo(2);
            registerFields[OperandType.RegisterST3] = new RegisterInfo(3);
            registerFields[OperandType.RegisterST4] = new RegisterInfo(4);
            registerFields[OperandType.RegisterST5] = new RegisterInfo(5);
            registerFields[OperandType.RegisterST6] = new RegisterInfo(6);
            registerFields[OperandType.RegisterST7] = new RegisterInfo(7);
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the register.
        /// </summary>
        public OperandType Register { get; private set; }
        /// <summary>
        /// Gets the type of local variable needed to store the operand value as a method argument.
        /// </summary>
        public override Type MethodArgType
        {
            get
            {
                RegisterInfo info;
                if(!registerFields.TryGetValue(this.Register, out info))
                    return null;

                Type valueType;
                if(info.RegisterType == RegisterType.Byte)
                    valueType = typeof(byte);
                else if(info.RegisterType == RegisterType.Word)
                {
                    if(this.WordSize == 2)
                        valueType = typeof(ushort);
                    else
                        valueType = typeof(uint);
                }
                else
                    valueType = typeof(double);

                if(this.ReturnType == EmitReturnType.Address)
                    valueType = valueType.MakePointerType();

                return valueType;
            }
        }
        #endregion

        #region Public Static Methods
        /// <summary>
        /// Returns a value indicating whether an operand is a register that is known at compile time.
        /// </summary>
        /// <param name="operand">Operand value to test.</param>
        /// <returns>Value indicating whether operand is a specific register.</returns>
        public static bool IsKnownRegister(OperandType operand)
        {
            return registerFields.ContainsKey(operand);
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Emits IL for the operation.
        /// </summary>
        public override void EmitLoad()
        {
            var info = registerFields[this.Register];

            LoadProcessor();

            if(info.RegisterType != RegisterType.FPU)
            {
                il.Emit(OpCodes.Ldfld, info.Field);

                if(this.ReturnType == EmitReturnType.Value)
                {
                    if(info.RegisterType == RegisterType.Byte)
                        il.Emit(OpCodes.Ldind_U1);
                    else
                        il.Emit(this.WordSize == 4 ? OpCodes.Ldind_U4 : OpCodes.Ldind_U2);
                }
            }
            else
            {
                if(this.ReturnType == EmitReturnType.Address)
                    throw new InvalidOperationException();

                il.Emit(OpCodes.Ldfld, Infos.Processor.FPU);

                il.LoadConstant(info.FPUIndex);
                il.Emit(OpCodes.Call, Infos.FPU.GetRegisterValue);
            }
        }
        #endregion

        #region Private Static Fields
        /// <summary>
        /// Stores information about the known registers.
        /// </summary>
        private static readonly Dictionary<OperandType, RegisterInfo> registerFields = new Dictionary<OperandType, RegisterInfo>();
        #endregion

        #region Private Structs
        /// <summary>
        /// Contains information about a register.
        /// </summary>
        private struct RegisterInfo
        {
            #region Constructors
            /// <summary>
            /// Initializes a new RegisterInfo struct.
            /// </summary>
            /// <param name="field">Pointer field on the processor type.</param>
            /// <param name="registerType">Type of the register.</param>
            public RegisterInfo(FieldInfo field, RegisterType registerType)
            {
                this.Field = field;
                this.RegisterType = registerType;
                this.FPUIndex = 0;
            }
            /// <summary>
            /// Initializes a new RegisterInfo struct.
            /// </summary>
            /// <param name="fpuIndex">Index of the FPU register.</param>
            public RegisterInfo(int fpuIndex)
            {
                this.Field = null;
                this.RegisterType = RegisterType.FPU;
                this.FPUIndex = fpuIndex;
            }
            #endregion

            #region Public Fields
            /// <summary>
            /// Pointer field on the processor type.
            /// </summary>
            public FieldInfo Field;
            /// <summary>
            /// Type of the register.
            /// </summary>
            public RegisterType RegisterType;
            /// <summary>
            /// The index of the register if it is on the FPU.
            /// </summary>
            public int FPUIndex;
            #endregion
        }
        #endregion
    }

    /// <summary>
    /// Specifies the type of a register.
    /// </summary>
    internal enum RegisterType
    {
        /// <summary>
        /// The register stores a byte.
        /// </summary>
        Byte,
        /// <summary>
        /// The register stores a word.
        /// </summary>
        Word,
        /// <summary>
        /// The register stores a double on the FPU.
        /// </summary>
        FPU
    }
}
